#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

using Osh;
using Osh.Configuration;
using Osh.Dvb;
using Osh.Media;
using Osh.Teletext;
using OshHost.Web;

namespace OshHost
{
	/// <summary>
	/// Summary description for MainForm.
	/// </summary>
	public partial class MainForm : System.Windows.Forms.Form
	{
		private static string OshOffLabel = "Off";
		private static string OshOnLabel = "On";
		private Size DefaultOshSize = new Size(720, 576);

		#region StreamParserState Class
		private class StreamParserState
		{
			private string _name;
			private IStreamParser _parser;
			private Exception _parserException;
			private DateTime _parserExceptionTimeUtc;
			private Exception _parsingException;
			private DateTime _parsingExceptionTimeUtc;
			private StatusBarPanel _statusControl;

			public StreamParserState(IStreamParser parser, string name, StatusBarPanel statusControl)
			{
				_parser = parser;
				_name = name;
				_statusControl = statusControl;
				_parser.Error += new EventHandler<System.IO.ErrorEventArgs>(Parser_Error);
				_parser.ParsingError += new EventHandler<System.IO.ErrorEventArgs>(Parser_ParsingError);
			}

			public IStreamParser Parser
			{
				get { return _parser; }
			}

			public Exception ParserException
			{
				get { return _parserException; }
				set { _parserException = value; }
			}

			public DateTime ParserExceptionTimeUtc
			{
				get { return _parserExceptionTimeUtc; }
				set { _parserExceptionTimeUtc = value; }
			}

			public Exception ParsingException
			{
				get { return _parsingException; }
				set { _parsingException = value; }
			}

			public DateTime ParsingExceptionTimeUtc
			{
				get { return _parsingExceptionTimeUtc; }
				set { _parsingExceptionTimeUtc = value; }
			}

			private void Parser_Error(object sender, System.IO.ErrorEventArgs e)
			{
				_parserException = e.GetException();
				_parserExceptionTimeUtc = DateTime.UtcNow;
				UpdateStatusControl();
			}

			private void Parser_ParsingError(object sender, System.IO.ErrorEventArgs e)
			{
				_parsingException = e.GetException();
				_parsingExceptionTimeUtc = DateTime.UtcNow;
				UpdateStatusControl();
			}

			public void UpdateStatusControl()
			{
				string message = string.Empty;
				if(_parser.IsRunning)
				{
					if(_parserExceptionTimeUtc.AddSeconds(3) < DateTime.UtcNow)
						_parserException = null;

					if(_parserException != null)
						message = _name + ": error";
					else
					{
						if(_parsingExceptionTimeUtc.AddSeconds(3) < DateTime.UtcNow)
							_parsingException = null;

						message = _parsingException == null ? _name + ": working..." : _name + ": working (error)...";
					}
				}
				else
					message = _name + ": stoped";
				_statusControl.Text = message;
			}

			public StatusBarPanel StatusControl
			{
				get { return _statusControl; }
			}
		}
		#endregion StreamParserState Class

		private Rectangle _normalBounds;
		private Timer _statusBarUpdateTimer;
		private Timer _stateSyncTimer;
		private Timer _statusBarAutoHideTimer;
		private OshApplication _osh;
		private WebBrowserEx _oshBrowser;
		private List<View> _views = new List<View>();
		private List<StreamParserState> _parsers = new List<StreamParserState>();
		private Dictionary<Shortcut, object> _shortcuts = new Dictionary<Shortcut,object>();

		private string _screenName = null;
		private bool _trackSize = false;

		public MainForm()
		{
			InitializeComponent();
			InitializeOshModeMenu();
			InitializeOshScreenMenu();
			InitializeComponentEx();
			InitializeViews();
			InitializeTeletextLanguageMenu();
			Application.DoEvents();

			_osh = (OshApplication)Host.Current.Osh;
			_osh.SetBounds(0, 0, DefaultOshSize.Width, DefaultOshSize.Height);
			_osh.Show += new EventHandler(Osh_Show);
			_osh.Hide += new EventHandler(Osh_Hide);
			_osh.BoundsChanged += new EventHandler(Osh_BoundsChanged);
			_osh.ModeChanged += new EventHandler<ModeChangeEventArgs>(Osh_ModeChanged);
			_osh.ScreenChange += new EventHandler<ScreenChangeArgs>(Osh_ScreenChange);
			UpdateSizeStatusBar();
			UpdateModeStatusBar();
			Application.DoEvents();

			_osh.MediaController.Start();
			UpdateOshModeMenu();

			Host.Current.ActivateHost += new ActivateHostHandler(Host_ActivateHost);
			Host.Current.Services.RegisterService(typeof(Osh.Epg.IEpgProvider), new Osh.Dvb.EpgProvider());
			Host.Current.Services.RegisterService(typeof(Osh.Teletext.ITeletextProvider),
				new Osh.Teletext.TeletextProvider(_osh.MediaController.TeletextParser.TeletextService));
			

			_parsers.Add(new StreamParserState(_osh.MediaController.EpgParser, "EPG", this.epgStateStatusBarPanel));
			_parsers.Add(new StreamParserState(_osh.MediaController.TeletextParser, "Teletext", this.teletextStateStatusBarPanel));

			_stateSyncTimer = new Timer();
			_stateSyncTimer.Interval = (int)TimeSpan.FromSeconds(5).TotalMilliseconds;
			_stateSyncTimer.Tick += new EventHandler(StateSyncTimer_Tick);

			_statusBarUpdateTimer = new Timer();
			_statusBarUpdateTimer.Interval = (int)TimeSpan.FromSeconds(1).TotalMilliseconds;
			_statusBarUpdateTimer.Tick += new EventHandler(StatusBarUpdateTimer_Tick);

			_statusBarAutoHideTimer = new Timer();
			_statusBarAutoHideTimer.Interval = (int)TimeSpan.FromSeconds(5).TotalMilliseconds;
			_statusBarAutoHideTimer.Tick += new EventHandler(StatusBarAutoHideTimer_Tick);
		}

		#region Initialization/Destruction
		private void InitializeComponentEx()
		{
			this.oshVisibilityStatusBarPanel.Tag = false;
			this.oshVisibilityStatusBarPanel.Text = OshOffLabel;

			this.oshModeAutoSwitchMenuItem.Checked = Configuration.Global.Osh.AutoSwitchMode;
			Program.ShowErrors = this.oshSettingsShowErrorsMenuItem.Checked = Configuration.OshHost.ShowErrors;
			this.oshSettingsShowScriptErrorsMenuItem.Checked = Configuration.OshHost.ShowScriptErrors;
			this.epgEnabledMenuItem.Checked = Configuration.Global.Osh.Epg.Enabled;
			this.teletextEnabledMenuItem.Checked = Configuration.Global.Osh.Teletext.Enabled;
			this.playbackCaptureScreenshotsMenuItem.Checked = Configuration.Global.Osh.Playback.CaptureScreenshots;
		}

		private void InitializeOsh()
		{
			this.oshPanel.SuspendLayout();

			_oshBrowser = new WebBrowserEx();
			_oshBrowser.Dock = DockStyle.None;
			_oshBrowser.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left;
			_oshBrowser.Location = new Point(0, 0);
			_oshBrowser.Size = DefaultOshSize;
			_oshBrowser.MinimumSize = DefaultOshSize;
			_oshBrowser.TabIndex = 0;
			_oshBrowser.ScriptError += new EventHandler<Osh.Scripting.ScriptErrorEventArgs>(WebBrowser_ScriptError);

			this.oshPanel.Controls.Add(_oshBrowser);
			this.oshPanel.ResumeLayout(false);

			_oshBrowser.Navigate("about:blank");

			Host.Current.WebBrowser = _oshBrowser;
			Host.Current.WebBrowser.ObjectForScripting = Host.Current;

			_osh.OshSource.Register(Host.Current.SourceName);

			// OshBrowser with the OSH source has to be registered before OSH Host
			Host.Current.Register();

			WebHost.Current.RegisterOshProtocol();

			// OSH Host has to be registered before navigating the OSH Control because it is used by script
			_oshBrowser.Navigate(Configuration.Local.HomePageUrl);
		}

		private void InitializeOshModeMenu()
		{
			foreach(ModeElement modeElement in Configuration.Global.Osh.Modes)
			{
				MenuItem menuItem = new MenuItem(modeElement.Name);
				menuItem.Tag = modeElement.Name;
				menuItem.Shortcut = ParseShortcut(modeElement.Shortcut);
				if(!string.IsNullOrEmpty(modeElement.Player))
					menuItem.Enabled = Configuration.Global.Osh.IsMediaPlayerEnabled(modeElement.Player);
				menuItem.RadioCheck = true;
				menuItem.Click += new EventHandler(OshModeMenuItem_Click);
				this.oshModeMenuItem.MenuItems.Add(menuItem);

				if(menuItem.Shortcut != Shortcut.None)
					_shortcuts[menuItem.Shortcut] = menuItem;
			}
		}

		/// <summary>
		/// Disables all OSH modes with unavailable players.
		/// </summary>
		private void UpdateOshModeMenu()
		{
			int modeNumber = 1;
			List<MenuItem> removeItems = new List<MenuItem>();
			foreach(MenuItem menuItem in this.oshModeMenuItem.MenuItems)
			{
				string mode = (menuItem.Tag as string);
				if(string.IsNullOrEmpty(mode))
					continue;
				
				ModeElement modeElement;
				if(Configuration.Global.Osh.Modes.TryGetModeByName(mode, out modeElement) &&
					!string.IsNullOrEmpty(modeElement.Player))
				{
					if(!_osh.MediaController.IsMediaPlayerAvailable(modeElement.Player))
					{
						menuItem.Enabled = false;
						//removeItems.Add(menuItem);
						//continue;
					}
				}

				if(menuItem.Shortcut == Shortcut.None)
				{
					while(modeNumber <= 9)
					{
						Shortcut shortcut = (Shortcut)(modeNumber + (int)Shortcut.CtrlShift0);
						modeNumber++;
						if(!_shortcuts.ContainsKey(shortcut))
						{
							menuItem.Shortcut = shortcut;
							_shortcuts[shortcut] = menuItem;
							break;
						}
					}
				}
			}

			foreach(MenuItem menuItem in removeItems)
				this.oshModeMenuItem.MenuItems.Remove(menuItem);
		}

		private void InitializeOshScreenMenu()
		{
			foreach(Osh.Configuration.ScreenElement screenElement in Configuration.Global.Osh.Screens)
			{
				MenuItem menuItem = new MenuItem(screenElement.Name);
				menuItem.Tag = screenElement.Name;
				menuItem.Shortcut = ParseShortcut(screenElement.Shortcut);
				menuItem.RadioCheck = true;
				menuItem.Click += new EventHandler(OshScreenMenuItem_Click);
				this.oshScreenMenuItem.MenuItems.Add(menuItem);

				if(menuItem.Shortcut != Shortcut.None)
					_shortcuts[menuItem.Shortcut] = menuItem;
			}
		}

		private void InitializeViews()
		{
			_views.Add(new View(this.viewOshMenuItem, InitializeOshView));
			_views.Add(new View(this.viewConsoleMenuItem, InitializeConsoleView));
			_views.Add(new View(this.viewLogMenuItem, InitializeLogView));

			_shortcuts[this.viewOshMenuItem.Shortcut] = this.viewOshMenuItem;
			_shortcuts[this.viewConsoleMenuItem.Shortcut] = this.viewConsoleMenuItem;
			_shortcuts[this.viewLogMenuItem.Shortcut] = this.viewLogMenuItem;
		}

		private void InitializeOshView(View view)
		{
			view.Container = this.oshPanel;
			view.WebBrowser = Host.Current.WebBrowser;
			InitializeWebBrowserShortcuts(view.WebBrowser);
		}

		private void InitializeConsoleView(View view)
		{
			WebBrowserEx consoleWebBrowser = new WebBrowserEx();
			consoleWebBrowser.Dock = DockStyle.Fill;

			consoleWebBrowser.Navigate("about:blank");

			view.Container = consoleWebBrowser;
			view.WebBrowser = consoleWebBrowser;
			view.WebBrowser.ObjectForScripting = Host.Current;
			InitializeWebBrowserShortcuts(view.WebBrowser);

			consoleWebBrowser.Navigate(Configuration.Local.ConsolePageUrl);
			this.borderPanel.Controls.Add(consoleWebBrowser);
		}

		private void InitializeLogView(View view)
		{
			WebBrowserEx logWebBrowser = new WebBrowserEx();
			logWebBrowser.Dock = DockStyle.Fill;
			
			logWebBrowser.Navigate("about:blank");

			view.Container = logWebBrowser;
			view.WebBrowser = logWebBrowser;
			view.WebBrowser.ObjectForScripting = Host.Current;
			InitializeWebBrowserShortcuts(view.WebBrowser);

			Host.Current.TraceWebBrowser = view.WebBrowser;

			logWebBrowser.Navigate(Configuration.Local.LogPageUrl);
			this.borderPanel.Controls.Add(logWebBrowser);
		}

		private void InitializeTeletextLanguageMenu()
		{
			foreach(MenuItem item in teletextDefaultLanguageMenuItem.MenuItems)
			{
				item.Click += new EventHandler(TeletextDefaultLanguageItem_Click);
				if(int.Parse((string)item.Tag) == Configuration.Global.Osh.Teletext.DefaultLanguage)
					item.Checked = true;
			}
		}

		private void InitializeWebBrowserShortcuts(WebBrowserEx webBrowser)
		{
			webBrowser.EnabledShortcuts.Add(Shortcut.AltF4);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlA);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlC);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlV);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlX);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlIns);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlDel);
			webBrowser.EnabledShortcuts.Add(Shortcut.ShiftIns);
			webBrowser.EnabledShortcuts.Add(Shortcut.ShiftDel);
			webBrowser.EnabledShortcuts.Add(Shortcut.F2);
			webBrowser.EnabledShortcuts.Add(Shortcut.F3);
			webBrowser.EnabledShortcuts.Add(Shortcut.F4);
			webBrowser.EnabledShortcuts.Add(Shortcut.F5);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlF);
			webBrowser.EnabledShortcuts.Add(Shortcut.CtrlShiftL);
			webBrowser.EnabledShortcuts.Add(Shortcut.Del);

			webBrowser.WebBrowserShortcutsEnabled = false;
			webBrowser.TranslateAccelerator += 
				new EventHandler<TranslateAcceleratorEventArgs>(WebBrowser_TranslateAccelerator);
		}

		private Shortcut ParseShortcut(string shortcut)
		{
			try
			{
				return (Shortcut)Enum.Parse(typeof(Shortcut), shortcut);
			}
			catch
			{
				return Shortcut.None;
			}
		}

		/// <summary>
		/// Called on unhandled script errors.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void WebBrowser_ScriptError(object sender, Osh.Scripting.ScriptErrorEventArgs e)
		{
			e.ContinueRunningScript = true;

			StringBuilder sb = new StringBuilder();
			sb.AppendFormat("Script error. Line: {0}. Char: {1}. Code: {2}. Url: {3}. Message: ",
				e.Error.Line, e.Error.Character, e.Error.Code, e.Error.Url);
			sb.Append(e.Error.Message);

			string errorText = sb.ToString();
			Log.Error((Host.Current.ScriptTraceSource as Osh.Scripting.ScriptTraceSource), errorText);
			if(Configuration.OshHost.ShowScriptErrors)
				Program.ProcessException(new Exception(errorText), false);
		}

		private void WebBrowser_TranslateAccelerator(object sender, TranslateAcceleratorEventArgs e)
		{
			WebBrowser webBrowser = (WebBrowser)sender;

			Shortcut shortcut = (Shortcut)(e.Message.wParam.ToInt32() | (int)Control.ModifierKeys);

			if(_shortcuts.ContainsKey(shortcut))
			{
				object obj = _shortcuts[shortcut];
				if(obj is MenuItem)
					(obj as MenuItem).PerformClick();
			}
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if(disposing)
			{
				DisposeViews();
				Host.Current.WebBrowser = null;
				Host.Current.TraceWebBrowser = null;
				Host.Current.Unregister();
				if(_osh != null && _osh.OshSource != null)
					_osh.OshSource.Unregister();

				if(components != null)
					components.Dispose();
			}
			base.Dispose(disposing);
		}

		private void DisposeViews()
		{
			for(int i = 0; i < _views.Count; i++)
			{
				if(_views[i].WebBrowser != null)
				{
					_views[i].WebBrowser.Dispose();
					_views[i].WebBrowser = null;
				}
			}
			_views.Clear();
		}
		#endregion Initialization/Destruction

		#region Host/Osh/MediaController Events
		private void Host_ActivateHost()
		{
			if(this.WindowState == FormWindowState.Minimized || !this.Visible)
				RestoreFromSystemTray();
			else
				this.Activate();
		}

		private void Osh_ScreenChange(object sender, ScreenChangeArgs e)
		{
			_screenName = e.ScreenName;
		}

		private void Osh_BoundsChanged(object sender, EventArgs e)
		{
			if(Host.Current.WebBrowser == null)
				return;

			Control control = Host.Current.WebBrowser;
			int width = _osh.Left + _osh.Width;
			int height = _osh.Top + _osh.Height;
			control.MinimumSize = new Size(width, height);

			BoundsSpecified boundsSpec = BoundsSpecified.None;
			if(control.Width < control.MinimumSize.Width)
				boundsSpec |= BoundsSpecified.Width;
			if(control.Height < control.MinimumSize.Height)
				boundsSpec |= BoundsSpecified.Height;
			control.SetBounds(0, 0, width, height, boundsSpec);

			UpdateSizeStatusBar();
		}

		private void Osh_ModeChanged(object sender, ModeChangeEventArgs e)
		{
			UpdateModeStatusBar();
		}

		private void Osh_Show(object sender, EventArgs e)
		{
			this.oshVisibilityStatusBarPanel.Tag = true;
			this.oshVisibilityStatusBarPanel.Text = OshOnLabel;
		}

		private void Osh_Hide(object sender, EventArgs e)
		{
			this.oshVisibilityStatusBarPanel.Tag = false;
			this.oshVisibilityStatusBarPanel.Text = OshOffLabel;
		}
		#endregion Host/Osh/MediaController Events

		#region State Synchronization
		private void StateSyncTimer_Tick(object sender, EventArgs e)
		{
		}
		#endregion State Synchronization

		#region General Form Events
		protected override unsafe void WndProc(ref Message m)
		{
			if(m.Msg == Win32.WM_WINDOWPOSCHANGING)
			{
				Win32.WINDOWPOS* windowPos = (Win32.WINDOWPOS*)m.LParam;
				if((windowPos->flags & ~(Win32.SWP_NOSIZE | Win32.SWP_NOMOVE)) == windowPos->flags &&
					windowPos->x == -32000 && windowPos->y == -32000)
				{
					// This form is about to be minimized.
					// Block minimization because when Windows minimizes a window
					// it moves it out of sight, and the video overlay stops working.
					windowPos->flags |= (Win32.SWP_NOSIZE | Win32.SWP_NOMOVE);
				}
			}
			base.WndProc(ref m);
		}

		private void MainForm_Load(object sender, System.EventArgs e)
		{
			this.notifyIcon.Icon = this.Icon;
			this.notifyIcon.Visible = true;

			ResizeWindowToOshSize(DefaultOshSize);

			_statusBarUpdateTimer.Start();
			_stateSyncTimer.Start();

			InitializeOsh();
			SetView(viewOshMenuItem);
		}

		private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			try
			{
				if(_oshBrowser != null)
				{
					_osh.Visible = false;
					_oshBrowser.Navigate("about:blank");
					Application.DoEvents();
				}
			}
			catch { }
		}

		private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
		{
			_statusBarUpdateTimer.Stop();
			_stateSyncTimer.Stop();
			_statusBarAutoHideTimer.Stop();

			_osh.MediaController.Stop();
		}

		private void MainForm_Resize(object sender, System.EventArgs e)
		{
			if(this.WindowState == FormWindowState.Minimized)
			{
				// Don't use ShowInTaskbar!
				// It recreates handles and spoils activex hosting.
				this.Visible = false;
			}
			else if(this.WindowState == FormWindowState.Maximized)
			{
				ResizeOshBrowser();
			}
			else if(this.WindowState == FormWindowState.Normal)
			{
				_normalBounds = this.Bounds;
				if(_trackSize)
					SetStatusBarMessage(
						string.Format("Internal size: {0}x{1}", this.oshPanel.Width, this.oshPanel.Height));
				else
					ResizeOshBrowser();
			}
		}

		private void MainForm_ResizeBegin(object sender, System.EventArgs e)
		{
			_trackSize = true;
		}

		private void MainForm_ResizeEnd(object sender, System.EventArgs e)
		{
			_trackSize = false;

			ResizeOshBrowser();
		}
		#endregion General Form Events

		#region Tray Menu
		private void notifyIcon_DoubleClick(object sender, System.EventArgs e)
		{
			RestoreFromSystemTray();
		}

		private void trayOpenMenuItem_Click(object sender, System.EventArgs e)
		{
			RestoreFromSystemTray();
		}

		private void trayShowOsdMenuItem_Click(object sender, System.EventArgs e)
		{
			_osh.Visible = true;
		}

		private void trayHideOsdMenuItem_Click(object sender, System.EventArgs e)
		{
			_osh.Visible = false;
		}

		private void trayExitMenuItem_Click(object sender, System.EventArgs e)
		{
			this.Close();
		}
		#endregion Tray Menu

		#region File Menu
		private void fileExitMenuItem_Click(object sender, System.EventArgs e)
		{
			this.Close();
		}
		#endregion File Menu

		#region View Menu
		private void viewOshMenuItem_Click(object sender, EventArgs e)
		{
			SetView(sender as MenuItem);
		}

		private void viewConsoleMenuItem_Click(object sender, EventArgs e)
		{
			SetView(sender as MenuItem);
		}

		private void viewLogMenuItem_Click(object sender, EventArgs e)
		{
			SetView(sender as MenuItem);
		}
		
		private void viewRefreshMenuItem_Click(object sender, EventArgs e)
		{
			View view = GetCurrentView();
			if(view != null)
				view.WebBrowser.Refresh();
		}
		#endregion View Menu

		#region OSH Menu
		private void oshShowOsdMenuItem_Click(object sender, EventArgs e)
		{
			_osh.Visible = true;
		}

		private void oshHideOsdMenuItem_Click(object sender, EventArgs e)
		{
			_osh.Visible = false;
		}

		private void oshModeMenuItem_Popup(object sender, EventArgs e)
		{
			foreach(MenuItem menuItem in this.oshModeMenuItem.MenuItems)
			{
				if(menuItem.Tag is string)
				{
					if((menuItem.Tag as string) == _osh.Mode)
						menuItem.Checked = true;
					else
						menuItem.Checked = false;
				}
			}
		}

		private void oshModeAutoSwitchMenuItem_Click(object sender, System.EventArgs e)
		{
			oshModeAutoSwitchMenuItem.Checked = !oshModeAutoSwitchMenuItem.Checked;
			Configuration.Global.Osh.AutoSwitchMode = oshModeAutoSwitchMenuItem.Checked;
			Configuration.Global.Save();
		}

		private void OshModeMenuItem_Click(object sender, EventArgs e)
		{
			string modeName = (string)((MenuItem)sender).Tag;
			SetOshMode(modeName);
		}

		private void oshScreenMenuItem_Popup(object sender, EventArgs e)
		{
			foreach(MenuItem menuItem in this.oshScreenMenuItem.MenuItems)
			{
				if(menuItem.Tag is string)
				{
					if((menuItem.Tag as string) == _screenName)
						menuItem.Checked = true;
					else
						menuItem.Checked = false;
				}
			}
		}

		private void OshScreenMenuItem_Click(object sender, EventArgs e)
		{
			string screenName = (string)((MenuItem)sender).Tag;
			Host.Current.SendOshCommand("screen:" + screenName);
		}

		private void oshSettingsShowErrorsMenuItem_Click(object sender, EventArgs e)
		{
			oshSettingsShowErrorsMenuItem.Checked = !oshSettingsShowErrorsMenuItem.Checked;
			Program.ShowErrors = Configuration.OshHost.ShowErrors = oshSettingsShowErrorsMenuItem.Checked;
			Configuration.Global.Save();
		}

		private void oshSettingsShowScriptErrorsMenuItem_Click(object sender, EventArgs e)
		{
			oshSettingsShowScriptErrorsMenuItem.Checked = !oshSettingsShowScriptErrorsMenuItem.Checked;
			Configuration.OshHost.ShowScriptErrors = oshSettingsShowScriptErrorsMenuItem.Checked;
			Configuration.Global.Save();
		}
		#endregion OSH Menu

		#region DVB Menu
		private void playbackCaptureScreenshotsMenuItem_Click(object sender, EventArgs e)
		{
			playbackCaptureScreenshotsMenuItem.Checked = !playbackCaptureScreenshotsMenuItem.Checked;
			Configuration.Global.Osh.Playback.CaptureScreenshots = playbackCaptureScreenshotsMenuItem.Checked;
			Configuration.Global.Save();
		}

		private void epgEnabledMenuItem_Click(object sender, System.EventArgs e)
		{
			epgEnabledMenuItem.Checked = !epgEnabledMenuItem.Checked;
			Configuration.Global.Osh.Epg.Enabled = epgEnabledMenuItem.Checked;
			Configuration.Global.Save();
		}

		private void epgLogEitStreamMenuItem_Click(object sender, System.EventArgs e)
		{
			epgLogEitStreamMenuItem.Checked = !epgLogEitStreamMenuItem.Checked;
			_osh.MediaController.EpgParser.LogEitStream = epgLogEitStreamMenuItem.Checked;
		}

		private void epgLogEitStreamErrorsMenuItem_Click(object sender, System.EventArgs e)
		{
			epgLogEitStreamErrorsMenuItem.Checked = !epgLogEitStreamErrorsMenuItem.Checked;
			_osh.MediaController.EpgParser.LogEitStreamErrors = epgLogEitStreamErrorsMenuItem.Checked;
		}

		private void teletextEnabledMenuItem_Click(object sender, EventArgs e)
		{
			teletextEnabledMenuItem.Checked = !teletextEnabledMenuItem.Checked;
			Configuration.Global.Osh.Teletext.Enabled = teletextEnabledMenuItem.Checked;
			Configuration.Global.Save();
		}

		private void TeletextDefaultLanguageItem_Click(object sender, EventArgs e)
		{
			MenuItem item = (MenuItem)sender;
			int tag = int.Parse((string)item.Tag);

			_osh.MediaController.TeletextParser.TeletextService.DefaultCharset = new Osh.Teletext.Charset((byte)tag);
			Configuration.Global.Osh.Teletext.DefaultLanguage = tag;
			Configuration.Global.Save();

			foreach(MenuItem i in teletextDefaultLanguageMenuItem.MenuItems)
				i.Checked = (i == item);
		}

		private void teletextSavePageMenuItem_Click(object sender, EventArgs e)
		{
			string pageHtml = null;
			ITeletextProvider provider = Host.Current.Services.GetService<ITeletextProvider>();
			TeletextPage page = provider.CurrentPage;
			if(page != null)
				pageHtml = page.GetPageHtml();

			if(string.IsNullOrEmpty(pageHtml))
			{
				MessageBox.Show("Please wait until selected teletext page is received or choose another one on Teletext screen.",
					"Teletext page not found.", MessageBoxButtons.OK, MessageBoxIcon.Information);
				return;
			}

			if(string.IsNullOrEmpty(saveFileDialog.InitialDirectory))
				saveFileDialog.InitialDirectory = Environment.GetFolderPath(folderBrowserDialog.RootFolder);

			string address = new DisplayPageAddress(page.Address).ToString();
			saveFileDialog.FileName = Path.Combine(saveFileDialog.InitialDirectory, address + ".htm");

			if(saveFileDialog.ShowDialog() == DialogResult.OK)
			{
				folderBrowserDialog.SelectedPath = Path.GetDirectoryName(saveFileDialog.FileName);
				saveFileDialog.InitialDirectory = folderBrowserDialog.SelectedPath;
				string cssFilePath = saveFileDialog.FileName + ".css";

				File.WriteAllText(saveFileDialog.FileName, 
					string.Format(Properties.Resources.TeletextTemplate, Path.GetFileName(cssFilePath), pageHtml), Encoding.UTF8);

				File.WriteAllText(cssFilePath, Properties.Resources.TeletextStyles, Encoding.UTF8);
			}
			
			SetStatusBarMessage(string.Format("Teletext page {0} has been saved.", address));
		}

		private void teletextSaveAllPagesMenuItem_Click(object sender, EventArgs e)
		{
			if(folderBrowserDialog.ShowDialog() == DialogResult.OK)
			{
				saveFileDialog.InitialDirectory = folderBrowserDialog.SelectedPath;

				string cssFilePath = Path.Combine(folderBrowserDialog.SelectedPath, "teletext.css");
				File.WriteAllText(cssFilePath, Properties.Resources.TeletextStyles, Encoding.UTF8);

				TeletextService service = _osh.MediaController.TeletextParser.TeletextService;
				int pageCount = 0;
				for(int m = 0; m < service.Magazines.Length; m++)
				{
					TeletextMagazine magazine = service.Magazines[m];
					if(magazine == null)
						continue;
					List<PageAddress> pageAddresses = magazine.GetPageAddresses();
					foreach(PageAddress pageAddress in pageAddresses)
					{
						TeletextPage page;
						if(!magazine.TryGetPage(pageAddress, out page))
							continue;

						string address = new DisplayPageAddress(page.Address).ToString();
						string pagePath = Path.Combine(folderBrowserDialog.SelectedPath, address + ".htm");
						string pageHtml = page.GetPageHtml();

						File.WriteAllText(pagePath, 
							string.Format(Properties.Resources.TeletextTemplate, Path.GetFileName(cssFilePath), pageHtml),
							Encoding.UTF8);
						pageCount++;
					}
				}
				SetStatusBarMessage(string.Format("{0} teletext pages have been saved.", pageCount));
			}
		}

		private void teletextClearMenuItem_Click(object sender, EventArgs e)
		{
			_osh.MediaController.TeletextParser.TeletextService.Clear();
		}
		#endregion DVB Menu

		#region StatusBar
		private void statusBar_PanelClick(object sender, StatusBarPanelClickEventArgs e)
		{
			if(e.Button == MouseButtons.Left && e.Clicks == 2)
			{
				foreach(StreamParserState parser in _parsers)
				{
					if(e.StatusBarPanel == parser.StatusControl)
					{
						if(parser.ParserException != null)
							Program.ProcessException(parser.ParserException);
						else if(parser.ParsingException != null)
							Program.ProcessException(parser.ParsingException);
						return;
					}
				}
				
				if(e.StatusBarPanel == this.oshVisibilityStatusBarPanel)
				{
					_osh.Visible = !(bool)this.oshVisibilityStatusBarPanel.Tag;
				}
				else if(e.StatusBarPanel == this.sizeStatusBarPanel)
				{
					if(_oshBrowser != null)
					{
						ResizeWindowToOshSize(_oshBrowser.MinimumSize);
						SetStatusBarMessage(
							string.Format("OSH screen size: {0}x{1}", this.oshPanel.Width, this.oshPanel.Height));
					}
				}
			}
		}

		private void StatusBarUpdateTimer_Tick(object sender, EventArgs e)
		{
			foreach(StreamParserState parser in _parsers)
				parser.UpdateStatusControl();
		}

		private void StatusBarAutoHideTimer_Tick(object sender, EventArgs e)
		{
			_statusBarAutoHideTimer.Stop();
			this.messageStatusBarPanel.Text = string.Empty;
		}

		private void SetStatusBarMessage(string message)
		{
			this.messageStatusBarPanel.Text = message;
			_statusBarAutoHideTimer.Stop();
			_statusBarAutoHideTimer.Start();
		}
		#endregion StatusBar

		private void SetOshMode(string modeName)
		{
			Host.Current.Osh.Mode = modeName;
		}

		private void RestoreFromSystemTray()
		{
			this.Visible = true;
			if(this.WindowState == FormWindowState.Minimized)
			{
				this.WindowState = FormWindowState.Normal;
				this.Bounds = _normalBounds;
			}
			this.Activate();
		}

		private void ResizeOshBrowser()
		{
			if(_oshBrowser == null)
				return;

			Size availableSize = this.oshPanel.Size;
			bool vScrollbarRequired = _oshBrowser.MinimumSize.Height > availableSize.Height;
			bool hScrollbarRequired = _oshBrowser.MinimumSize.Width > availableSize.Width;
			if(vScrollbarRequired)
				availableSize.Width = this.oshPanel.ClientSize.Width;
			if(hScrollbarRequired)
				availableSize.Height = this.oshPanel.ClientSize.Height;

			BoundsSpecified boundsSpec = BoundsSpecified.None;
			if(availableSize.Width > _oshBrowser.Width)
				boundsSpec |= BoundsSpecified.Width;
			else if(availableSize.Width < _oshBrowser.Width)
			{
				boundsSpec |= BoundsSpecified.Width;
				if(availableSize.Width < _oshBrowser.MinimumSize.Width)
					availableSize.Width = _oshBrowser.MinimumSize.Width;
			}

			if(_oshBrowser.Height < availableSize.Height)
				boundsSpec |= BoundsSpecified.Height;
			else if(availableSize.Height < _oshBrowser.Height)
			{
				boundsSpec |= BoundsSpecified.Height;
				if(availableSize.Height < _oshBrowser.MinimumSize.Height)
					availableSize.Height = _oshBrowser.MinimumSize.Height;
			}

			_oshBrowser.SetBounds(0, 0, availableSize.Width, availableSize.Height, boundsSpec);
		}

		private void ResizeWindowToOshSize(Size oshMinSize)
		{
			Size newSize = oshMinSize +
				SystemInformation.FrameBorderSize + SystemInformation.FrameBorderSize +
				SystemInformation.Border3DSize + SystemInformation.Border3DSize +
				new Size(0 /*SystemInformation.VerticalScrollBarWidth*/,
				SystemInformation.CaptionHeight + SystemInformation.MenuHeight + statusBar.Height);

			if(_oshBrowser != null)
				_oshBrowser.Size = oshMinSize;

			this.Bounds = new Rectangle(
				(SystemInformation.WorkingArea.Width - newSize.Width) / 2,
				(SystemInformation.WorkingArea.Height - newSize.Height) / 2,
				newSize.Width, newSize.Height);
			_normalBounds = this.Bounds;
		}

		private void UpdateModeStatusBar()
		{
			this.modeStatusBarPanel.Text = _osh.Mode;
		}

		private void UpdateSizeStatusBar()
		{
			this.sizeStatusBarPanel.Text = string.Format("{0}x{1}", _osh.Width, _osh.Height);
		}

		private View GetCurrentView()
		{
			foreach(View view in _views)
				if(view.MenuItem.Checked)
					return view;
			return null;
		}

		private void SetView(MenuItem menuItem)
		{
			SetView(menuItem.Tag as View);
		}

		private void SetView(View view)
		{
			foreach(View nextView in _views)
			{
				if(nextView == view)
				{
					if(!nextView.Initialized)
						nextView.Initialize();
					nextView.Activate();
					nextView.MenuItem.Checked = true;
				}
				else
				{
					nextView.MenuItem.Checked = false;
				}
			}
		}
	}

	internal delegate void ViewInitializeHandler(View view);

	internal class View
	{
		private MenuItem _menuItem;
		private ViewInitializeHandler _initializer;
		private bool _initialized;
		private Control _container;
		private WebBrowserEx _webBrowser;

		public View(MenuItem menuItem, ViewInitializeHandler initializer)
		{
			menuItem.Tag = this;
			_menuItem = menuItem;
			_initializer = initializer;
		}

		public MenuItem MenuItem
		{
			get { return _menuItem; }
		}

		public Control Container
		{
			get { return _container; }
			set { _container = value; }
		}

		public WebBrowserEx WebBrowser
		{
			get { return _webBrowser; }
			set { _webBrowser = value; }
		}

		public bool Initialized
		{
			get { return _initialized; }
		}

		public void Initialize()
		{
			if(_initialized)
				throw new InvalidOperationException();

			if(_initializer != null)
				_initializer(this);
			_initialized = true;
		}

		public void Activate()
		{
			_container.BringToFront();
			_webBrowser.Focus();
		}
	}

}
